package org.nod.atgm;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.nod.atgm.gameobjects.AbstractGameObject;
import org.nod.atgm.gameobjects.ArtilleryShell;
import org.nod.atgm.gameobjects.ExplosionObject;
import org.nod.atgm.gameobjects.GeometryUtil;
import org.nod.atgm.gameobjects.IGameObject;
import org.nod.atgm.gameobjects.IRemovable;
import org.nod.atgm.gameobjects.MovingObject;
import org.nod.atgm.gameobjects.StatefulGameObject;
import org.nod.atgm.gameobjects.TexturedGameObject;
import org.nod.atgm.gameobjects.WorldGameObject;
import org.nod.atgm.model.CollisionManager;
import org.nod.atgm.model.GameCollisionEvent;
import org.nod.atgm.model.IGameCollisionListener;
import org.nod.atgm.model.PrecompiledObjectManager;
import org.nod.atgm.model.SpawnManager;
import org.nod.atgm.sound.SoundManager;

import android.app.Activity;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Vibrator;
import android.view.MotionEvent;

import com.threed.jpct.Camera;
import com.threed.jpct.Config;
import com.threed.jpct.FrameBuffer;
import com.threed.jpct.Light;
import com.threed.jpct.Matrix;
import com.threed.jpct.Object3D;
import com.threed.jpct.Primitives;
import com.threed.jpct.SimpleVector;
import com.threed.jpct.Texture;
import com.threed.jpct.TextureManager;
import com.threed.jpct.World;
import com.threed.jpct.util.MemoryHelper;
import com.threed.jpct.util.SkyBox;

/**
 * This class is responsible for running game loop, processing game events, sensor input handling etc.
 * @author 32kda
 *
 */
public class GameManager implements IGameCollisionListener {
	
	private static final float VERTICAL_ANGLE_LIMIT = 0.40f;
	private static final int Y_DEMULTIPLIER = 200;
	private static final int X_DEMULTIPLIER = 150;
	private static final String BOTTOM_ID = "bottom";
	private static final String TOP_ID = "top";
	private static final String RIGHT_ID = "right";
	private static final String LEFT_ID = "left";
	private static final String BACK_ID = "back";
	private static final String FRONT_ID = "front";
	public static final int EXPLOSION_SOUND_ID = 1;
	public static final int BIG_EXPLOSION_SOUND_ID = 2;
	public static final int ARTILLERY_SOUND_ID = 3;
	final int TICKS_PER_SECOND = 50;
	final int SKIP_TICKS = 1000 / TICKS_PER_SECOND;
	
	public static final float CAM_X = 0f;
	public static final float CAM_Z = -50f;
	public static SimpleVector CAM_CENTER = new SimpleVector(CAM_X,-1,CAM_Z);
	
	/**
	 * Game loop thread
	 * @author 32kda
	 */
	protected class GameLoop extends Thread {
		
		
		public GameLoop() {
			super("GameLoop");
		}
		
		@Override
		public void run() {
			long ticks = System.currentTimeMillis();
			while (!isPaused()) {
				long current = System.currentTimeMillis();
				if (current - ticks < SKIP_TICKS) {
					try {
						sleep(SKIP_TICKS - (current - ticks));
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				updateGame(System.currentTimeMillis() - ticks);
				ticks = current;
			}
			super.run();
		}
		
		public boolean isPaused () {
			return false;
		}
	}

	public static final SimpleVector INITIAL_VECTOR_UP = new SimpleVector(0f,-1f,0f);
	SimpleVector vectorUp = new SimpleVector(0f,-1f,0f);
	
	protected World world;
	protected Light sun;
	protected final Activity context;
	protected List<TexturedGameObject> objects = new ArrayList<TexturedGameObject>();
	protected List<IGameObject> toRemoveObjects = new ArrayList<IGameObject>();
	protected List<IGameObject> toAddObjects = new ArrayList<IGameObject>();
	private float sensorX;
	private float sensorY;
	private float initialY = Float.MIN_VALUE;
	private SimpleVector cameraVector = new SimpleVector(0f,0f,1f);
	private SimpleVector orthoVector = new SimpleVector();
	private SimpleVector targetVector = new SimpleVector();
	private Matrix cameraMatrix = new Matrix();
	private float angle = 0;
	
	protected int shotCount = 0;
	protected Vibrator vibrator;
	protected SoundManager soundManager;
	protected ArtilleryShell shell = null;
	protected Texture crosshairTexture;
	private SkyBox skyBox;
	private float horizAngle = 0;

	public GameManager(Activity context) {
		this.context = context;
		vibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
		soundManager = new SoundManager(context);
		loadTextures();
		loadSounds();
		loadPrecopiledModels();
		CollisionManager.addGameCollisionListener(this);
//		CollisionManager.addDestroyableCollisionListener(this);
	}
	
	protected void updateGame(long delta) {
		//Handle timer by game objects (shells, enemies, etc)
		synchronized (objects) {
			for (TexturedGameObject object : objects) {
				object.handleTimer((int) delta);
//				if (object instanceof Projectile)
//					object.getObject().checkForCollisionSpherical(((Projectile) object).getSpeedVector(),1);
				if (object instanceof IRemovable && ((IRemovable) object).needsToRemove())
					toRemoveObjects.add(object);
			}
		}
		
		//Manage accelerometer camera rotation
		if (Math.abs(sensorX) > 0.3) {
			float value = -sensorX / X_DEMULTIPLIER;
			if (Math.abs(horizAngle + value) < 2) {
				horizAngle += value;
				cameraVector.rotateY(value);
			}
		}
		Camera camera = world.getCamera();
		if (initialY == Float.MIN_VALUE || initialY == 0.0)
			initialY = sensorY;
		float yDiff = sensorY - initialY;
		if (Math.abs(yDiff) > 0.3) {
			angle -= yDiff / Y_DEMULTIPLIER;
			if (angle > VERTICAL_ANGLE_LIMIT) //Limit vertical angle
				angle = VERTICAL_ANGLE_LIMIT;
			if (angle < -VERTICAL_ANGLE_LIMIT)
				angle = -VERTICAL_ANGLE_LIMIT;		
		}
		vectorUp.set(INITIAL_VECTOR_UP); //Re-init camera up-vector before rotating it to necessary angle 
		orthoVector.x = cameraVector.z; //Calculate vector orthogonal to camera vector 
		orthoVector.z = -cameraVector.x;
		calcRotMatrix(cameraMatrix,orthoVector,angle); //Calculate rotation matrix
		vectorUp.rotate(cameraMatrix); //rotate camera up vector
		camera.setOrientation(cameraVector,vectorUp); //Set camera orientation
		
		//Handle shell firing, if any
		if (shell != null) {
			fireShell(shell);
			CollisionManager.registerProjectile(shell);
			shell = null;
		}
		
		//Spawn new enemies if needed
		StatefulGameObject[] newObjects = SpawnManager.spawnDestroyableObjects(delta);
		for (int i = 0; i < newObjects.length; i++) {
			world.addObject(((AbstractGameObject)newObjects[i]).getObject());
			CollisionManager.registerDestroyableObject(((AbstractGameObject)newObjects[i]));
			synchronized (objects) {
				objects.add((TexturedGameObject) newObjects[i]);
			}
		}
	}
	
	/**
	 * Calculates rotation matrix for arbitrary axis
	 * @param matrix Matrix to fill 
	 * @param rotVector Arbitrary rotation axis vector 
	 * @param angle Rotation angle
	 */
	protected void calcRotMatrix(Matrix matrix, SimpleVector rotVector, float angle) {
		matrix.setColumn(3,0,0,0,1);
		matrix.setRow(3,0,0,0,1);
		double sin = Math.sin(angle);
		double cos = Math.cos(angle);
		double subcos = 1 - cos;
		float x = rotVector.x;
		float y = rotVector.y;
		float z = rotVector.z;
		
		matrix.set(0,0,(float) (cos + subcos*x*x));
		matrix.set(0,1,(float) (subcos*x*y - sin*z));
		matrix.set(0,2,(float) (subcos*x*z + sin*y));
		
		matrix.set(1,0,(float) (subcos*y*x + sin*z));
		matrix.set(1,1,(float) (cos + subcos*y*y));
		matrix.set(1,2,(float) (subcos*y*z - sin*x));
		
		matrix.set(2,0,(float) (subcos*z*x - sin*y));
		matrix.set(2,1,(float) (subcos*z*y + sin*x));
		matrix.set(2,2,(float) (cos + subcos*z*z));
	}
	
	protected void loadTextures() {
		// Create a texture out of the icon...:-)
		Texture texture;
		try {
			texture = new Texture(context.getAssets().open("terrain.png"));
			TextureManager.getInstance().addTexture("texture", texture);
			
			texture = new Texture(context.getAssets().open("ani2.png"),true);
			TextureManager.getInstance().addTexture("explode", texture);
			
			texture = new Texture(context.getAssets().open(FRONT_ID+".png"));
			TextureManager.getInstance().addTexture(FRONT_ID, texture);
			texture = new Texture(context.getAssets().open(BACK_ID+".png"));
			TextureManager.getInstance().addTexture(BACK_ID, texture);
			texture = new Texture(context.getAssets().open(LEFT_ID+".png"));
			TextureManager.getInstance().addTexture(LEFT_ID, texture);
			texture = new Texture(context.getAssets().open(RIGHT_ID+".png"));
			TextureManager.getInstance().addTexture(RIGHT_ID, texture);
			texture = new Texture(context.getAssets().open(TOP_ID+".png"));
			TextureManager.getInstance().addTexture(TOP_ID, texture);
			texture = new Texture(context.getAssets().open(BOTTOM_ID+".png"));
			TextureManager.getInstance().addTexture(BOTTOM_ID, texture);
			
			texture = new Texture(context.getAssets().open("grass2.png"),true);
			texture.enableClamping();
			TextureManager.getInstance().addTexture("grass", texture);
			crosshairTexture = new Texture(context.getAssets().open("target.png"));
			TextureManager.getInstance().addTexture("target", crosshairTexture);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

	protected void loadSounds() {
		soundManager.addSound(EXPLOSION_SOUND_ID,R.raw.explosion);
		soundManager.addSound(BIG_EXPLOSION_SOUND_ID,R.raw.explosion_big);
		soundManager.addSound(ARTILLERY_SOUND_ID,R.raw.artillery);
	}

	protected void loadPrecopiledModels() {
		PrecompiledObjectManager.initPrecompiledObjects(context);
	}

	public World createWorld() {
		if (world == null) {
			world = new World();
			world.setAmbientLight(20, 20, 20);
	
			sun = new Light(world);
			sun.setIntensity(250, 250, 250);
	
			Object3D plane = Primitives.getPlane(32,15);
			plane.rotateX((float) (Math.PI / 2));
//			plane.calcTextureWrap();
			plane.setTexture("texture");
			plane.strip();
			plane.build();
			plane.setCollisionMode(Object3D.COLLISION_CHECK_OTHERS);
			plane.setName("plane");
			CollisionManager.registerWorldObject(new WorldGameObject(plane));
			
			world.addObject(plane);
			world.addObjects(createGrass());
			
//			Object3D box = Primitives.getBox(5,5);
//			box.calcTextureWrap();
//			box.setTexture("texture");
//			world.addObject(box);
//			box.setCollisionMode(Object3D.COLLISION_CHECK_OTHERS);
//			CollisionManager.registerWorldObject(new WorldGameObject(box));
	
			Camera cam = world.getCamera();
			cam.moveCamera(Camera.CAMERA_MOVEOUT, 20);
			cam.setPosition(CAM_CENTER);
	
			SimpleVector sv = new SimpleVector(0,0,0);
			sv.y -= 100;
			sv.z -= 100;
			sun.setPosition(sv);
			
//			world.setFogging(World.FOGGING_ENABLED);
			Light additional = new Light(world);
			additional.setIntensity(200, 200, 200);
			additional.setPosition(new SimpleVector(CAM_X,-20,CAM_Z));
			
			

			MemoryHelper.compact();
			Config.collideOffset = 100;
		}
		return world;
	}
	
	private Object3D[] createGrass() {
		Object3D[] polygons = GeometryUtil.createCircularRandomPolygons(CAM_CENTER,20,40,10,0,4,1);
		for (int i = 0; i < polygons.length; i++) {
		    polygons[i].setTransparency(100);
		    polygons[i].setTexture("grass");
		}
		
//		Object3D plane = GeometryUtil.createPlane(0,4,1,new SimpleVector(0,0,-1));
//
//	    plane.translate(0,0,-15);
//	    plane.setTransparency(100);
//	    plane.setTexture("grass");
		return polygons;
	}

	public SkyBox getSkyBox() {
		if (skyBox == null) {
			skyBox = new SkyBox(LEFT_ID,FRONT_ID,RIGHT_ID,BACK_ID,TOP_ID,BOTTOM_ID,220);
//			skyBox.setCenter(new SimpleVector(0,-5f,0));
		}
		return skyBox;		
	}

	public void startGameLoop() {
		GameLoop gameLoop = new GameLoop();
		gameLoop.setPriority(Thread.MAX_PRIORITY);
		gameLoop.start();
	}

	public void handleSensor(float mSensorX, float mSensorY) {
		this.sensorX = mSensorX;
		this.sensorY = mSensorY;
		
	}

	public void handleTouchUp(MotionEvent me) {
		shell = new ArtilleryShell();
	}

	private void fireShell(ArtilleryShell shell) {
		targetVector.set(cameraVector);
		targetVector.rotate(cameraMatrix);
		shell.setScalarSpeed(0.02f);
		shell.setGravityCoef(0.000007f);
		shell.setSpeedVector(targetVector.normalize());
		shell.setName("shell" + shotCount++);
		world.addObject(shell.getObject());
		synchronized (objects) {
			objects.add(shell);
		}
		playSound(ARTILLERY_SOUND_ID);
//		shell.addCollisionListener(this);
	}

//	@Override
//	public void collision(CollisionEvent event) {
//		boolean worldCollision = event.getTargets()[0].getName().equals("plane");
//		if (worldCollision) {
//			
//		}
//	}
//
//	@Override
//	public boolean requiresPolygonIDs() {
//		// TODO Auto-generated method stub
//		return false;
//	}
	
	protected void vibrate(long milliseconds) {
		vibrator.vibrate(milliseconds);
	}

	/**
	 * Perform updates to scene, which needed to be done in UI thread
	 */
	public void updateUI() {
		synchronized (objects) {
			for (IGameObject object : toRemoveObjects) {
				objects.remove(object);
				if (object instanceof MovingObject)
					CollisionManager.unregisterProjectile(object);
				Object3D object3d = object.getObject();
				try {
					world.removeObject(object3d);
				} catch (RuntimeException e) {
					// TODO: handle exception
				}
			}
			toRemoveObjects.clear();
			for (IGameObject object : toAddObjects) {
				objects.add((TexturedGameObject) object);
				world.addObject(object.getObject());
			}
			toAddObjects.clear();
		}
	}

	@Override
	public void handleProjectileWorldCollisionEvent(GameCollisionEvent event) {
		removeShell(event);
		playSound(EXPLOSION_SOUND_ID);
		toAddObjects.add(createExplosion(((Object3D) event.movingObject.getObject()).getTranslation()));
	}

	protected void removeShell(GameCollisionEvent event) {
		IGameObject toRemove = event.movingObject;
			
//			for (PhysicalGameObject gameObject : objects) {
//				if (gameObject.getName() == event.getObject().getName())
//					toRemove = gameObject;
//			}
		if (toRemove != null) {
			System.out.println("Remove: " + toRemove.getName());
//				toRemove.removeCollisionListener(this);
			toRemoveObjects.add(toRemove);
		}
	}

	protected void playSound(final int soundId) {
		context.runOnUiThread(new Runnable() {
			
			@Override
			public void run() {
				new AsyncTask<Object,Object,Object>() {
					
					@Override
					protected Object doInBackground(Object... params) {
						soundManager.playSound(soundId);
						return null;
					}
				}.execute();
			}
		});
	}
	
	@Override
	public void handleProjectileDestroyableCollisionEvent(GameCollisionEvent event) {
		removeShell(event);
		IGameObject toRemove = event.hitObject;
		if (toRemove != null)
			toRemoveObjects.add(toRemove);
		playSound(BIG_EXPLOSION_SOUND_ID);
		toAddObjects.add(createExplosion(toRemove.getObject().getTranslation()));
	}

	protected IGameObject createExplosion(SimpleVector translation) {
		ExplosionObject explosionObject = new ExplosionObject(translation, CAM_X, CAM_Z);
		explosionObject.translate(translation);
		explosionObject.setTexture("explode");
		
		return explosionObject;
	}

	public void postWorldDraw(FrameBuffer frameBuffer) {
		int x = Math.round(frameBuffer.getCenterX() - (crosshairTexture.getWidth() / 2));
		int y = Math.round(frameBuffer.getCenterY() - (crosshairTexture.getHeight() / 2));
		frameBuffer.blit(crosshairTexture,0,0,x,y,crosshairTexture.getWidth(),crosshairTexture.getHeight(),true);
	}


}
